﻿using AutoMapper;
using BJoin.Application.BObject;
using BJoin.Application.Dto;
using BJoin.Application.Entity;
using BJoin.Application.IRepository;
using BJoin.Common;
using BJoin.DapperExtension.DatabaseFactory;
using BJoin.DapperExtension.Linq;
using BJoin.IDGenEx;
using DapperExtensions;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;

namespace BJoin.Application.IManager
{
    public abstract class PocoManager<TDto, TEntity, TKey, TExt> : ShardingDataProvider<IDatabase, long?>, IPocoManager<TDto, TEntity, TKey, TExt>
         where TEntity : Entity<TKey, TExt> where TDto : ResDto<TKey, TExt> where TExt : BObjExt
    {
        /// <summary>
        /// DI Container Provider
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }
        protected IBaseRepository<TEntity, TKey, TExt> Repository { get; }
        /// <summary>
        /// Automapper's Mapper
        /// </summary>
        protected IMapper Mapper { get; }

        protected PocoManager(ILogger<PocoManager<TDto,TEntity,TKey,TExt>> logger, IServiceProvider provider, IBaseRepository<TEntity, TKey, TExt> repository, IMapper mapper, IDbFactory<IDatabase, long?> factory):base(logger,factory)
        {
            ServiceProvider = provider;
            Repository = repository;
            Mapper = mapper;
        }
        public virtual async Task<long> CountAsync<TFilterDto>(TFilterDto filter) where TFilterDto : ReqFilterDto<TExt>
        {
            return await ReadAsync( async database=> await Repository.CountAsync(database,filter));
        }

        public virtual async Task<long> CountAsync(Expression<Func<TEntity, bool>> expression)
        {
            IPredicateGroup predicate = Expression2Predicate<TEntity>.FromExpression(expression);
            return await ReadAsync(async database => await Repository.CountAsync(database,predicate));
        }

        public virtual async Task<TDto> GetSingleAsync(TKey id)
        {
            TEntity entity = await ReadAsync(async database => await Repository.GetAsync(database,id));
            return Mapper.Map<TDto>(entity);
        }
        public virtual async Task<TDto> GetSingleAsync(TKey id, Action<TDto> actTranslater)
        {
            var dto = await GetSingleAsync(id);
            if (null != dto)
                actTranslater(dto);
            return dto;
        }
        public virtual async Task<IEnumerable<TDto>> FilterAsync<TFilterDto>(TFilterDto filter, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var entities = await ReadAsync(async database => await Repository.GetListAsync(database,filter, orderBy));
            var dtos = Mapper.Map<IEnumerable<TDto>>(entities);
            return dtos;
        }
        public virtual async Task<IEnumerable<TDto>> FilterAsync<TFilterDto>(TFilterDto filter, Action<List<TDto>> actTranslater, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var dtos = await FilterAsync(filter, orderBy);
            if (dtos.IsAny())
                actTranslater(dtos.ToList());
            return dtos;
        }

        public virtual async Task<IEnumerable<TDto>> FilterAsync(Expression<Func<TEntity, bool>> expression, string[] orderBy = null)
        {
            IPredicateGroup predicate = Expression2Predicate<TEntity>.FromExpression(expression);
            var entities = await ReadAsync(async database => await Repository.GetListAsync(database,predicate, orderBy));
            return Mapper.Map<IEnumerable<TDto>>(entities);
        }

        public virtual async Task<IEnumerable<TDto>> LimitFilterAsync<TFilterDto>(TFilterDto filter, int firstResult, int maxResults, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var entities = await ReadAsync(async database => await Repository.GetListSetAsync(database,firstResult, maxResults, filter, orderBy));
            return Mapper.Map<IEnumerable<TDto>>(entities);
        }

        public virtual async Task<IEnumerable<TDto>> LimitFilterAsync(Expression<Func<TEntity, bool>> expression, int firstResult, int maxResults, string[] orderBy = null)
        {
            IPredicateGroup predicate = Expression2Predicate<TEntity>.FromExpression(expression);
            var entities = await ReadAsync(async database => await Repository.GetListSetAsync(database,firstResult, maxResults, predicate, orderBy));
            return Mapper.Map<IEnumerable<TDto>>(entities);
        }

        public virtual async Task<IEnumerable<TDto>> LimitFilterAsync<TFilterDto>(TFilterDto filter, int firstResult, int maxResults, Action<List<TDto>> actTranslater, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var entities = await ReadAsync(async database => await Repository.GetListSetAsync(database,firstResult, maxResults, filter, orderBy));
            var dtos = Mapper.Map<IEnumerable<TDto>>(entities);
            if (dtos.IsAny())
                actTranslater(dtos.ToList());
            return dtos;
        }

        public virtual async Task<ResPagingDto<TDto>> PagingFilterAsync<TFilterDto>(TFilterDto filter, int pageIndex, int pageSize, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var pagingEntities = await ReadAsync(async database => await Repository.GetListPagingAsync(database,pageIndex, pageSize, filter, orderBy));
            return Mapper.Map<ResPagingDto<TDto>>(pagingEntities);
        }

        public virtual async Task<ResPagingDto<TDto>> PagingFilterAsync(Expression<Func<TEntity, bool>> expression, int pageIndex, int pageSize, string[] orderBy = null)
        {
            IPredicateGroup predicate = Expression2Predicate<TEntity>.FromExpression(expression);
            var pagingEntities = await ReadAsync(async database => await Repository.GetListPagingAsync(database,pageIndex, pageSize, predicate, orderBy));
            return Mapper.Map<ResPagingDto<TDto>>(pagingEntities);
        }

        public virtual async Task<ResPagingDto<TDto>> PagingFilterAsync<TFilterDto>(TFilterDto filter, int pageIndex, int pageSize, Action<List<TDto>> actTranslater, string[] orderBy = null) where TFilterDto : ReqFilterDto<TExt>
        {
            var pagingEntities = await ReadAsync(async database => await Repository.GetListPagingAsync(database,pageIndex, pageSize, filter, orderBy));
            var pagingDto = Mapper.Map<ResPagingDto<TDto>>(pagingEntities);
            if (null != pagingDto)
                actTranslater(pagingDto.Items.ToList());
            return pagingDto;
        }
        public virtual async Task<IEnumerable<dynamic>> GetDynamicAsync(string sql)
        {
            return await ReadAsync(async database => await Repository.GetDynamicAsync(database,sql));
        }

        public virtual async Task<TKey> AddAsync<TAddDto>(TAddDto addDto) where TAddDto : ReqAddDto<TExt>
        {
            var entity = await AddDto2EntityAsync(addDto);
            return await WriteAsync(async database => await Repository.InsertAsync(database,entity));
        }
        public virtual async Task<IEnumerable<TKey>> AddManyAsync<TAddDto>(IEnumerable<TAddDto> addDtos) where TAddDto : ReqAddDto<TExt>
        {
            var entities = await AddDto2EntityAsync(addDtos);
            return await WriteAsync(async database => await Repository.InsertManyAsync(database,entities));

        }

        public virtual async Task<bool> UpdateSetAsync<TUpdateDto>(TKey id, TUpdateDto updateDto) where TUpdateDto : ReqUpdateDto<TExt>
        {
            await BizValidaterAsync(id, updateDto);
            var entity = Mapper.Map<TEntity>(updateDto);
            return await WriteAsync(async database => await Repository.UpdateSetAsync(database,entity, new { id }));
        }
        public virtual async Task<bool> UpdateAsync<TUpdateDto>(TKey id, TUpdateDto updateDto) where TUpdateDto : ReqUpdateDto<TExt>
        {
            await BizValidaterAsync(id, updateDto);
            var entity = Mapper.Map<TEntity>(updateDto);
            return await WriteAsync(async database => await Repository.UpdateAsync(database,entity, new { id }));
        }

        public virtual async Task<bool> DeleteAsync(TKey id)
        {
            return await WriteAsync(async database => await Repository.DeleteAsync(database,id));
        }

        public virtual async Task<bool> DeleteAsync<TFilterDto>(TFilterDto filter) where TFilterDto : ReqFilterDto<TExt>
        {
            return await WriteAsync(async database => await Repository.DeleteAsync(database,filter));
        }
        public virtual async Task<bool> DeleteAsync(Expression<Func<TEntity, bool>> expression)
        {
            IPredicateGroup predicate = Expression2Predicate<TEntity>.FromExpression(expression);
            return await WriteAsync(async database => await Repository.DeleteAsync(database,predicate));
        }
        public virtual async Task<bool> DeleteManyAsync(string ids)
        {
            return await WriteAsync(async database => await Repository.DeleteAsync(database,ids));
        }
        protected async virtual Task<TEntity> AddDto2EntityAsync<TAddDto>(TAddDto addDto) where TAddDto : ReqAddDto<TExt>
        {
            await BizValidaterAsync(addDto);
            var entity = Mapper.Map<TEntity>(addDto);
            if (typeof(TKey) == typeof(long))
            {
                var id = new IDGenerator(0).CreateId();
                entity.Id = (TKey)Convert.ChangeType(id, typeof(TKey));
            }
            return entity;
        }
        protected async virtual Task<IEnumerable<TEntity>> AddDto2EntityAsync<TAddDto>(IEnumerable<TAddDto> addDtos) where TAddDto : ReqAddDto<TExt>
        {
            await BizValidaterAsync(addDtos);
            var entities = Mapper.Map<IEnumerable<TEntity>>(addDtos);
            //assign Id for each item
            if (typeof(TKey) == typeof(long))
            {
                var ids = new IDGenerator(0).Take(addDtos.Count());
                foreach (var item in entities.Zip(ids, (a, b) => new { a, b }))
                {
                    item.a.Id = (TKey)Convert.ChangeType(item.b, typeof(TKey));
                }
            }
            return entities;
        }
        protected virtual async Task BizValidaterAsync<TAddDto>(TAddDto addDto) where TAddDto : ReqAddDto<TExt> => await Task.Run(() => NotImplemented(MethodBase.GetCurrentMethod().Name));
        protected virtual async Task BizValidaterAsync<TAddDto>(IEnumerable<TAddDto> addDtos) where TAddDto : ReqAddDto<TExt> => await Task.Run(() => NotImplemented(MethodBase.GetCurrentMethod().Name));
        protected virtual async Task BizValidaterAsync<TUpdateDto>(TKey id, TUpdateDto updateDto) where TUpdateDto : ReqUpdateDto<TExt> => await Task.Run(() => NotImplemented(MethodBase.GetCurrentMethod().Name));
        protected virtual void NotImplemented(string methodName)
        {
            throw new NotImplementedException($"{methodName} Missing implementation");
        }

    }
}
